package com.kinghood.productcenter.service.async.impl;

import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.kinghood.infrastructure.model.constant.CommonConstants;
import com.kinghood.infrastructure.util.base.IdGenUtil;
import com.kinghood.obs.ObsManger;
import com.kinghood.productcenter.common.model.dao.product.DownloadCenterDO;
import com.kinghood.productcenter.common.model.dao.product.ProductBaseDO;
import com.kinghood.productcenter.common.model.dto.base.ListProductQueryDTO;
import com.kinghood.productcenter.common.model.enums.product.BusinessLogOperateEnum;
import com.kinghood.productcenter.common.model.vo.product.ProductListVO;
import com.kinghood.productcenter.dao.product.ProductBaseMapper;
import com.kinghood.productcenter.service.async.IAsyncBusinessLogService;
import com.kinghood.productcenter.service.async.IAsyncHuaWeiYunService;
import com.kinghood.productcenter.service.async.IAsyncZipService;
import com.kinghood.productcenter.service.product.IDownloadCenterService;
import com.kinghood.productcenter.service.product.IProductBaseService;
import com.kinghood.productcenter.service.product.handler.ProductBaseHandler;
import com.kinghood.sharding.model.BasePage;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 描述:
 * 创建人：jp
 * 创建时间：2024/1/29 10:34
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class AsyncZipServiceImpl implements IAsyncZipService {
    private final String obsFix = "bussiness_center/product_center/product";
    private final String uploadPath = "/app/file";
    @Value("${obs.bucketName}")
    private String bucketName;
    private final ObsManger obsManger;
    private final IDownloadCenterService downloadCenterService;
    private final IProductBaseService productBaseService;
    private final IAsyncHuaWeiYunService asyncHuaWeiYun;
    private final ProductBaseMapper productBaseMapper;
    private final IAsyncBusinessLogService asyncBusinessLogService;


    /**
     * 上传压缩包中的图片给华伟云
     *
     * @param filePath             文件路径
     * @param existProductCodeList 现有产品代码列表
     * @param absolutePath         绝对路径
     * @param type                 类型
     * @param userId               用户id
     * @return
     * @author jp
     * @date 2024/01/24 17:58
     */
    @Async("taskExecutor")
    @Override
    public void uploadPicToHuaWeiYun(String filePath, List<String> existProductCodeList, String absolutePath, String type, Long userId,
                                     Long DLid) {
        if (filePath.endsWith("zip")) {
            uploadToHuaWeiYunZip(filePath, existProductCodeList, absolutePath, type, userId);
        } else if (filePath.endsWith("7z")) {
            uploadToHuaWeiYun7z(filePath, existProductCodeList, absolutePath, type, userId);
        }
        // 图片上传后，把压缩包也上传到华为云
        asyncHuaWeiYun.uploadToHuaWeiYun(DLid, filePath);
    }

    /**
     * 上传到华伟云zip
     *
     * @param filePath             文件路径
     * @param existProductCodeList 现有产品代码列表
     * @param absolutePath         绝对路径
     * @param type                 类型
     * @param userId               用户id
     * @return
     * @author jp
     * @date 2024/01/24 17:58
     */
    private void uploadToHuaWeiYunZip(String filePath, List<String> existProductCodeList, String absolutePath, String type, Long userId) {
        //设置格式 防止压缩包中文乱码
        try {
            FileInputStream input = new FileInputStream(filePath);
            ZipInputStream zin = new ZipInputStream(input, Charset.forName("GBK"));
            ZipEntry entry;
            Map<String, ProductBaseDO> productMap = new HashMap<>();
            Set<Long> logFlag = new HashSet<>();
            List<Long> businessIdList = new ArrayList<>();
            while ((entry = zin.getNextEntry()) != null) {
                if (!entry.isDirectory()) {
                    String entryName = entry.getName();
                    String productCode = getPictureName(entryName);
                    // 过滤不存在的产品图片
                    if (!existProductCodeList.contains(productCode)) {
                        continue;
                    }
                    String readPath = absolutePath + File.separator + entryName;
                    // 把文件保存到本地
                    OutputStream byteArrayOutputStream = new FileOutputStream(readPath);
                    byte[] array = new byte[1024];
                    int num = -1;
                    while ((num = zin.read(array, 0, array.length)) > -1) {
                        byteArrayOutputStream.write(array, 0, num);
                    }
                    byteArrayOutputStream.close();
                    File localFile = new File(readPath);

                    // 把要上传的图片解压到当前目录中
                    String huaweiSavePath = obsFix + "/" + IdGenUtil.getId() + entryName;
                    String savePath = "/" + huaweiSavePath;
                    obsManger.fileUpload(localFile, huaweiSavePath, bucketName);
//                    上传后把图片地址存入数据库中
                    ProductBaseDO productBaseDO = productMap.get(productCode);
                    if (!productMap.containsKey(productCode)) {
                        productBaseDO = new LambdaQueryChainWrapper<>(productBaseMapper)
                                .eq(ProductBaseDO::getStyleNumber, productCode)
                                .eq(ProductBaseDO::getIsDelete, CommonConstants.IS_DELETE_N)
                                .one();
                        productMap.put(productCode, productBaseDO);
                    }
                    if (Objects.nonNull(productBaseDO)) {
                        if (Objects.equals(type, "F")) {
                            String picture = productBaseDO.getFactoryPicture();
                            productBaseDO.setFactoryPicture(addPicture(picture, savePath));
                        } else if (Objects.equals(type, "M")) {
                            String picture = productBaseDO.getHandPicture();
                            productBaseDO.setHandPicture(addPicture(picture, savePath));
                        } else if (Objects.equals(type, "D")) {
                            String picture = productBaseDO.getHdPicture();
                            productBaseDO.setHdPicture(addPicture(picture, savePath));
                        } else if (Objects.equals(type, "S")) {
                            String picture = productBaseDO.getOperationPicture();
                            productBaseDO.setOperationPicture(addPicture(picture, savePath));
                        }
                        productBaseDO.setUpdateTime(LocalDateTime.now());
                        productBaseDO.setUpdateUserId(userId);
                        productBaseMapper.updateById(productBaseDO);
                        boolean deleted = new File(readPath).delete();
                        log.info("已成功删除文件：" + readPath + ", 结果：" + deleted);
                        if (!logFlag.contains(productBaseDO.getId())) {
                            businessIdList.add(productBaseDO.getId());
                            logFlag.add(productBaseDO.getId());
                        }
                    }
                }
                zin.closeEntry();
            }
            asyncBusinessLogService.addLogBusinessIdBatch(businessIdList,
                    null, BusinessLogOperateEnum.IMPORT_PICTURE.getType(), userId);
        } catch (Exception e) {
            log.error("上传异常：", e);
            e.printStackTrace();
        }
    }

    /**
     * 上传到华威云7z
     *
     * @param filePath             文件路径
     * @param existProductCodeList 现有产品代码列表
     * @param absolutePath         绝对路径
     * @param type                 类型
     * @param userId               用户id
     * @return
     * @author jp
     * @date 2024/01/24 17:59
     */
    private void uploadToHuaWeiYun7z(String filePath, List<String> existProductCodeList, String absolutePath, String type, Long userId) {
        //设置格式 防止压缩包中文乱码
        try {
            SevenZFile sevenZFile = new SevenZFile(new File(filePath));
            Map<String, ProductBaseDO> productMap = new HashMap<>();
            Set<Long> logFlag = new HashSet<>();
            List<Long> businessIdList = new ArrayList<>();
            SevenZArchiveEntry entry;
            while ((entry = sevenZFile.getNextEntry()) != null) {
                if (!entry.isDirectory()) {
                    String entryName = entry.getName();
                    String productCode = getPictureName(entryName);
                    // 过滤不存在的产品图片
                    if (!existProductCodeList.contains(productCode)) {
                        continue;
                    }
                    String readPath = absolutePath + File.separator + entryName;

                    try (OutputStream out = Files.newOutputStream(Paths.get(readPath));
                         BufferedOutputStream bos = new BufferedOutputStream(out)) {
                        int len = -1;
                        byte[] buf = new byte[1024];
                        while ((len = sevenZFile.read(buf)) != -1) {
                            bos.write(buf, 0, len);
                        }
                    } catch (IOException e) {
                        log.error(entryName + "文件创建失败");
                        continue;
                    }

                    File localFile = new File(readPath);

                    // 把要上传的图片解压到当前目录中
                    String huaweiSavePath = obsFix + "/" + IdGenUtil.getId() + entryName;
                    String savePath = "/" + huaweiSavePath;
                    obsManger.fileUpload(localFile, huaweiSavePath, bucketName);
//                    上传后把图片地址存入数据库中
                    ProductBaseDO productBaseDO = productMap.get(productCode);
                    if (!productMap.containsKey(productCode)) {
                        productBaseDO = new LambdaQueryChainWrapper<>(productBaseMapper)
                                .eq(ProductBaseDO::getStyleNumber, productCode)
                                .eq(ProductBaseDO::getIsDelete, CommonConstants.IS_DELETE_N)
                                .one();
                        productMap.put(productCode, productBaseDO);
                    }
                    if (Objects.nonNull(productBaseDO)) {
                        if (Objects.equals(type, "F")) {
                            String picture = productBaseDO.getFactoryPicture();
                            productBaseDO.setFactoryPicture(addPicture(picture, savePath));
                        } else if (Objects.equals(type, "M")) {
                            String picture = productBaseDO.getHandPicture();
                            productBaseDO.setHandPicture(addPicture(picture, savePath));
                        } else if (Objects.equals(type, "D")) {
                            String picture = productBaseDO.getHdPicture();
                            productBaseDO.setHdPicture(addPicture(picture, savePath));
                        } else if (Objects.equals(type, "S")) {
                            String picture = productBaseDO.getOperationPicture();
                            productBaseDO.setOperationPicture(addPicture(picture, savePath));
                        }
                        productBaseDO.setUpdateTime(LocalDateTime.now());
                        productBaseDO.setUpdateUserId(userId);
                        productBaseMapper.updateById(productBaseDO);
                        boolean deleted = new File(readPath).delete();
                        log.info("已成功删除文件：" + readPath + ", 结果：" + deleted);
                        if (!logFlag.contains(productBaseDO.getId())) {
                            businessIdList.add(productBaseDO.getId());
                            logFlag.add(productBaseDO.getId());
                        }
                    }
                }
            }
            sevenZFile.close();
            asyncBusinessLogService.addLogBusinessIdBatch(businessIdList,
                    null, BusinessLogOperateEnum.IMPORT_PICTURE.getType(), userId);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 添加图片
     *
     * @param DBData 相片
     * @author jp
     * @date 2023/12/29
     */
    private String addPicture(String DBData, String addPicture) {
        String picture = "";
        if (StringUtils.isEmpty(DBData)) {
            picture = addPicture;
        } else {
            picture = DBData + "," + addPicture;
        }
        return picture;
    }

    /**
     * 获取图片名称
     *
     * @param picture 相片
     * @return @return {@link String }
     * @author jp
     * @date 2024/01/24 11:03
     */
    private String getPictureName(String picture) {
        String name;
        if (!picture.contains("-")) {
            name = picture.substring(0, picture.lastIndexOf("."));
        } else {
            String endStr = picture.substring(picture.lastIndexOf("-") + 1, picture.lastIndexOf("."));
            if (!isNumeric(endStr)) {
                name = picture.substring(0, picture.lastIndexOf("."));
            } else {
                name = picture.substring(0, picture.lastIndexOf("-"));
            }
        }
        return name;
    }

    /**
     * 是否全为数字
     *
     * @param str str
     * @return @return boolean
     * @author jp
     * @date 2024/01/24 17:59
     */
    private static boolean isNumeric(String str) {
        // Check if the string is null or empty
        if (str == null || str.isEmpty()) {
            return false;
        }

        // Iterate over each character in the string
        for (char c : str.toCharArray()) {
            // Check if the character is a digit
            if (!Character.isDigit(c)) {
                return false;
            }
        }

        // If all characters are digits, return true
        return true;
    }

    /**
     * 异步导出产品图片
     *
     * @param param 参数
     * @return
     * @author jp
     * @date 2024/01/29 10:37
     */
    @Override
    @Async("taskExecutor")
    @Transactional(rollbackFor = Exception.class)
    public void asyncExportProductPicture(ListProductQueryDTO param, DownloadCenterDO downloadCenterDO) {
        // 设置默认分页参数
        if (Objects.isNull(param.getPage())) {
            param.setPage(new BasePage());
            param.getPage().setPageSize(100L);
        }

        // 获取产品列表
        Page<ProductListVO> result = productBaseService.listProduct(param);
        if (result.getTotal() == 0) {
            return;
        }

        // 创建文件夹
        String newFileName = downloadCenterDO.getFileName();
        String filePath = uploadPath + "/temp_" + downloadCenterDO.getId();
        File path = new File(filePath);
        if (!path.exists()) {
            path.mkdir();
        }

        // 开始下载图片
        log.info("图片批量下载开始-----------------");
        long start = System.currentTimeMillis();
        while (result.getCurrent() <= result.getPages()) {
            List<PictureIndex> pictureList = new ArrayList<>();
            for (ProductListVO record : result.getRecords()) {
                putInPictures(param.getPictureType(), record, pictureList);
            }
            pictureList.parallelStream().forEach(x -> {
                String picPath = x.getPicPath();
                if (picPath.startsWith("/")) {
                    // 使用substring()方法获取从索引1开始到最后的子字符串
                    picPath = picPath.substring(1);
                }
                try {
                    InputStream input = obsManger.downFile(null, picPath);
                    if (null == input) {
                        log.info("文件不存在：{}", picPath);
                        return;
                    }
                    BufferedInputStream bufferedInputStream = new BufferedInputStream(input);
                    // 创建输出流对象并写入数据
                    OutputStream output =
                            Files.newOutputStream(new File(filePath + "/" + x.getStyleNumber() + "-" + (x.getIndex()) + picPath.substring(picPath.lastIndexOf("."))).toPath());
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = bufferedInputStream.read(buffer)) > 0) {
                        output.write(buffer, 0, length);
                    }
                    // 关闭输入输出流
                    input.close();
                    output.close();
                } catch (IOException e) {
                    log.error("图片{}导出失败：{}", picPath, e);
                }
            });

            param.getPage().setPageNum(param.getPage().getPageNum() + 1);
            if (result.getCurrent() <= result.getPages()) {
                result = productBaseService.listProduct(param);
            }
        }
        log.info("图片批量下载结束-----------------");
        log.info("耗时：{}秒", (System.currentTimeMillis() - start) / 1000);
        // 数据打包
        String zipFile = putInZip(path, newFileName);
        // 上传到华为云
        File file = new File(zipFile);
        downloadCenterService.lambdaUpdate()
                .set(DownloadCenterDO::getFileSize, ProductBaseHandler.getSizeName(file.length()))
                .eq(DownloadCenterDO::getId, downloadCenterDO.getId())
                .update();
        asyncHuaWeiYun.syncuploadToHUaWeiYun(downloadCenterDO.getId(), file.getAbsolutePath());
        // 删除之前生成的文件
        deleteDirectory(path);
    }

    /**
     * 获取对象中的图片
     *
     * @param record      记录
     * @param pictureList 图片列表
     * @return
     * @author jp
     * @date 2024/01/29 10:37
     */
    private void putInPictures(String pictureType, ProductListVO record, List<PictureIndex> pictureList) {
        List<String> arrStr = new ArrayList<>();
        if ("hd".equals(pictureType) && !StringUtils.isEmpty(record.getHdPicture())) {
            arrStr.addAll(Arrays.asList(record.getHdPicture().split(",")));
        } else if ("hand".equals(pictureType) && !StringUtils.isEmpty(record.getHandPicture())) {
            arrStr.addAll(Arrays.asList(record.getHandPicture().split(",")));
        } else if ("factory".equals(pictureType) && !StringUtils.isEmpty(record.getFactoryPicture())) {
            arrStr.addAll(Arrays.asList(record.getFactoryPicture().split(",")));
        } else if ("operation".equals(pictureType) && !StringUtils.isEmpty(record.getOperationPicture())) {
            arrStr.addAll(Arrays.asList(record.getOperationPicture().split(",")));
        }
        if (!CollectionUtils.isEmpty(arrStr)) {
            int i = 1;
            for (String path : arrStr) {
                PictureIndex item = new PictureIndex();
                item.setPicPath(path);
                item.setIndex(i++);
                item.setStyleNumber(record.getStyleNumber());
                pictureList.add(item);
            }
        }
    }

    /**
     * 文件压缩
     *
     * @param sourcePath 源路径
     * @return @return {@link String }
     * @author jp
     * @date 2024/01/29 09:34
     */
    private String putInZip(File sourcePath, String zipFileName) {
        String fileName = "/" + zipFileName;
        String zipFilePath = uploadPath + fileName; // 目标压缩包路径

        try {
            //zip文件生成位置
            File zipFile = new File(zipFilePath);
            FileOutputStream fos = new FileOutputStream(zipFile);
            ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos));
            fileToZip(zos, sourcePath, "");
            zos.close();
            fos.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return zipFilePath;
    }

    /**
     * 文件夹压缩
     *
     * @param zos        zos
     * @param sourceFile 源文件
     * @param path       路径
     * @return
     * @author jp
     * @date 2024/01/29 10:19
     */
    private static void fileToZip(ZipOutputStream zos, File sourceFile, String path) throws Exception {
        //如果是文件夹只创建zip实体即可，如果是文件，创建zip实体后还要读取文件内容并写入
        if (sourceFile.isDirectory()) {
            path = path + sourceFile.getName() + "/";
            ZipEntry zipEntry = new ZipEntry(path);
            zos.putNextEntry(zipEntry);
            for (File file : sourceFile.listFiles()) {
                fileToZip(zos, file, path);
            }
        } else {
            //创建ZIP实体，并添加进压缩包
            ZipEntry zipEntry = new ZipEntry(path + sourceFile.getName());
            zos.putNextEntry(zipEntry);
            byte[] bufs = new byte[1024 * 10];
            //读取待压缩的文件并写进压缩包里
            FileInputStream fis = new FileInputStream(sourceFile);
            BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10);
            int read = 0;
            while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                zos.write(bufs, 0, read);
            }
            bis.close();
            fis.close();
        }
    }

    /**
     * 删除目录
     *
     * @param directory 目录
     * @return
     * @author jp
     * @date 2024/01/29 10:19
     */
    private static void deleteDirectory(File directory) {
        if (directory != null && directory.isDirectory()) {
            for (File file : directory.listFiles()) {
                if (file.isDirectory()) {
                    deleteDirectory(file);
                } else {
                    boolean deleted = file.delete();
                    log.info("已成功删除文件：" + file.getAbsolutePath() + ", 结果：" + deleted);
                }
            }

            boolean deleted = directory.delete();
            log.info("已成功删除文件夹：" + directory.getAbsolutePath() + ", 结果：" + deleted);
        }
    }

    @Data
    static
    class PictureIndex {
        private String picPath;
        private String styleNumber;
        private Integer index;
    }
}
